Skip to content

Fix removeEvent for native Shiny input containers - #283

Open
dajiaohuang wants to merge 1 commit into
daattali:masterfrom
dajiaohuang:repostew/remove-input-events-20261015
Open

dajiaohuang wants to merge 1 commit into
daattali:masterfrom
dajiaohuang:repostew/remove-input-events-20261015

Conversation

@dajiaohuang

@dajiaohuang dajiaohuang commented Oct 15, 2026 •

Copy link
Copy Markdown

Problem

removeEvent() does not remove handlers registered on native Shiny inputs such as textInput():

library(shiny)
library(shinyjs)
shinyApp(
  fluidPage(useShinyjs(), textInput("field", "Type here"),
            actionButton("remove", "Remove key handler"), textOutput("key")),
  function(input, output, session) {
    key <- reactiveVal("none")
    event_id <- onevent("keyup", "field", function(event) key(event$key))
    observeEvent(input$remove, {
      removeEvent(event_id, "field")
      key("removed")
    })
    output$key <- renderText(key())
  }
)

Type a key, click Remove, and type again: the supposedly removed callback still updates the output. Removing by event type has the same problem.

Attachment deliberately resolves native inputs to their .shiny-input-container, storing the handler IDs there. Removal looks for those IDs on the input itself and therefore leaves the handler active.

Change

Use the same existing container resolution in the existing-element removal path. This preserves selective instance removal, event-type removal, ordinary HTML elements and the queued dynamic-element path. Add a module fixture/regression covering instance removal without disturbing a second handler, removal by type, and ordinary paragraph events.

Validation

  • R 4.3.3 / Shiny 1.8.0 / Chrome, real loopback Shiny sessions with normal DOM clicks/keypresses: base fails, patched code passes.
  • The added fixture was exercised in Chrome using Playwright: on base the first/second input callbacks reach 3/3 despite removal, while patched values remain 1/2. Plain paragraph removal continues working; module namespaces are covered.
  • Guide form and two-module visibility/state/reset/onclick flows plus extension initialization/parameters also pass in the real Shiny browser runtime.
  • Source install, JavaScript syntax and diff checks pass.
  • R CMD build --no-build-vignettes --no-manual and R CMD check --no-manual --no-vignettes with _R_CHECK_FORCE_SUGGESTS_=false: no errors; two warnings about unbuilt vignette outputs and one note for unavailable suggested packages (rmarkdown, shinyAce, shinydisconnect, shinytest2). Non-browser tests pass; the shinytest2 runner itself and full cross-platform/vignette checks were not run locally.

Summary by CodeRabbit

  • Bug Fixes
    • Event handlers can now be reliably removed from Shiny inputs, including individual handlers and all handlers of a specified type.
  • Tests
    • Added coverage for removing input and element event handlers and verifying the resulting behavior.

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 846448b7-ab86-4774-a445-c875c359aba6
📥 Commits

Reviewing files that changed from the base of the PR and between 59a0fae and 26ecc25.

📒 Files selected for processing: 3
  • inst/srcjs/shinyjs-default-funcs.js
  • tests/testthat/apps/remove-events/app.R
  • tests/testthat/apps/remove-events/test-remove-events-shiny.R

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

📝 Walkthrough

removeEvent now resolves existing targets to the Shiny input container used by event attachment before accessing event data. A Shiny app test checks removal of a single handler by ID and removal of all handlers by event type.

Changes: Event handler removal

Layer / File(s) Summary
Resolve targets and test handler removal
inst/srcjs/shinyjs-default-funcs.js, tests/testthat/apps/remove-events/*
removeEvent resolves an existing target to its Shiny input container. The test app registers click handlers and checks their outputs after removing one handler or all handlers.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 26ecc

No actionable merge-blocking risk is identified. The change appears ready to merge after normal checks.

Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: fixing removeEvent behavior for native Shiny input containers.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit taps the input twice
Then asks one handler not to play
The rest keep counting as before
A second click says more, hooray
All click handlers pause their dance
The rabbit checks the count once more

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant